/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: Kernel exposed object to user
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-4-30      JasonHu           Init
 */

#include <base/exobj.h>
#include <base/malloc.h>
#include <base/debug.h>
#define NX_LOG_LEVEL NX_LOG_ERROR
#include <base/log.h>
#include <base/thread.h>

NX_PRIVATE const char * exobj_name_table[NX_EXOBJ_TYPE_NR] = {
    "None",
    "reserved",
    "thread",
    "snapshot",
    "mutex",
    "semaphore",
    "device",
    "timer",
    "udriver",
    "hub",
    "shm",
    "shmaddr",
    "file",
    "dir",
    "ipc",
    "ipc_channel",
    "fifo",    
    "condition",    
    "msgqueue",    
    "pipe",
    "uobject",
};

const char * NX_ExposedObjectGetNameByType(NX_ExposedObjectType type)
{
    if (type >= NX_EXOBJ_TYPE_NR)
    {
        return exobj_name_table[0];
    }
    return exobj_name_table[type];
}

NX_Error NX_ExposedObjectTableInit(NX_ExposedObjectTable * table)
{
    int i;

    if (!table)
    {
        return NX_EINVAL;
    }

    /* alloc array */
    table->maxCount = NX_EXOBJ_INIT_NR;
    table->objectArray = NX_MemAlloc(table->maxCount * NX_EXOBJ_OBJPTR_SIZE);
    if (table->objectArray == NX_NULL)
    {
        return NX_ENOMEM;
    }

    table->usedCount = 0;
    NX_SpinInit(&table->lock);
    for (i = 0; i < table->maxCount; i++)
    {
        table->objectArray[i] = NX_NULL;
    }
    return NX_EOK;
}

/**
 * exit object table no need free table, because just exit table, not destroy table
 */
NX_Error NX_ExposedObjectTableExit(NX_ExposedObjectTable * table)
{
    NX_Solt solt;
    NX_Error err;

    if (!table)
    {
        return NX_EINVAL;
    }

    /* uninstall all objects */
    for (solt = table->usedCount - 1; solt >= 0; solt--)
    {
        err = NX_ExposedObjectUninstalll(table, solt);
        if (err != NX_EOK)
        {
            NX_LOG_E("exit table faield! %d->%s", solt, NX_ErrorToString(err));
        }
    }

    /**
     * free object array
     */
    NX_MemFree(table->objectArray);
    table->usedCount = 0;
    table->maxCount = 0;

    return NX_EOK;
}

NX_Error NX_ExposedObjectTableDump(NX_ExposedObjectTable * table)
{
    int i;
    NX_ExposedObject * object;

    if (!table)
    {
        return NX_EINVAL;
    }
    for (i = 0; i < table->usedCount; i++)
    {
        object = table->objectArray[i];
        if (object)
        {
            NX_LOG_D("exobj table[%d] object=%p, type=%s, close handler:%p", i, object->object,
                NX_ExposedObjectGetNameByType(object->type), object->close);
        }
    }
    return NX_EOK;
}

NX_ExposedObject * NX_ExposedObjectCreate(void)
{
    NX_ExposedObject * exobj;
    exobj = NX_MemAlloc(sizeof(NX_ExposedObject));
    if (!exobj)
    {
        return NX_NULL;
    }
    exobj->type = NX_EXOBJ_NONE;
    exobj->close = NX_NULL;
    exobj->copy = NX_NULL;
    exobj->object = NX_NULL;
    NX_AtomicSet(&exobj->reference, 0);
    return exobj;
}

NX_Error NX_ExposedObjectDestroy(NX_ExposedObject * exobj)
{
    if (!exobj)
    {
        return NX_EINVAL;
    }
    exobj->type = NX_EXOBJ_NONE;
    exobj->close = NX_NULL;
    exobj->object = NX_NULL;
    NX_AtomicSet(&exobj->reference, 0);

    NX_MemFree(exobj);
    return NX_EOK;
}

NX_PRIVATE NX_Error ExpandObjectTable(NX_ExposedObjectTable * table)
{
    NX_ExposedObject **newObjectArray; 
    NX_Size newSize;

    newSize = table->maxCount + NX_EXOBJ_APPEND_NR;

    /* The existing objects exceed the maximum value and cannot be reassigned */
    if (newSize > NX_EXOBJ_MAX_NR)
    {
        return NX_ENORES;
    }

    newObjectArray = NX_MemAlloc(newSize * NX_EXOBJ_OBJPTR_SIZE);
    if (newObjectArray == NX_NULL)
    {
        return NX_ENOMEM;
    }

    NX_MemZero(newObjectArray, newSize * NX_EXOBJ_OBJPTR_SIZE);

    /* copy old solt */
    NX_MemCopy(newObjectArray, table->objectArray, table->maxCount * NX_EXOBJ_OBJPTR_SIZE);

    /* free old object array */
    NX_MemFree(table->objectArray);

    table->maxCount = newSize;
    table->objectArray = newObjectArray;

    return NX_EOK;
}

NX_Solt NX_ExposedObjectGetFreeSolt(NX_ExposedObjectTable * table)
{
    NX_Solt i;
    NX_ASSERT(table);  

tryGet:
    for (i = 0; i < table->maxCount; i++)
    {
        if (table->objectArray[i] == NX_NULL)
        {
            return i;
        }
    }

    if (ExpandObjectTable(table) == NX_EOK)
    {
        goto tryGet;
    }

    return NX_SOLT_INVALID_VALUE;
}

NX_PRIVATE void NX_ExposedObjectRefAndBind(NX_ExposedObjectTable * table, NX_Solt solt, NX_ExposedObject * exobj)
{
    NX_AtomicInc(&exobj->reference);

    table->objectArray[solt] = exobj;
    if (solt >= table->usedCount)
    {
        table->usedCount++;
    }
}

NX_Solt NX_ExposedObjectAlloc(NX_ExposedObjectTable * table)
{
    NX_ASSERT(table);
    
    NX_Solt i;
    NX_ExposedObject * object;

    i = NX_ExposedObjectGetFreeSolt(table);
    if (i < 0)
    {
        return -1;
    }

    object = NX_ExposedObjectCreate();
    if (!object)
    {
        return -1;
    }

    NX_ExposedObjectRefAndBind(table, i, object);

    return i;
}

NX_Solt NX_ExposedObjectAllocFrom(NX_ExposedObjectTable * table, NX_Solt solt)
{
    NX_ASSERT(table);
    
    NX_ExposedObject * object;

    object = NX_ExposedObjectCreate();
    if (!object)
    {
        return -1;
    }

    NX_ExposedObjectRefAndBind(table, solt, object);

    return solt;
}

/**
 * exobj maybe referenced by more than one solt, so need solt here.
 */
NX_Error NX_ExposedObjectFree(NX_ExposedObjectTable * table, NX_ExposedObject * exobj, NX_Solt solt)
{
    NX_ASSERT(table);
    
    if (solt >= table->usedCount)
    {
        return NX_EINVAL;
    }

    if (table->objectArray[solt] != exobj)
    {
        return NX_ENORES;
    }
    if (NX_AtomicGet(&exobj->reference) <= 0)
    {
        NX_ExposedObjectDestroy(exobj);
    }
    table->objectArray[solt] = NX_NULL;

    return NX_EOK;
}

NX_ExposedObject * NX_ExposedObjectGet(NX_ExposedObjectTable * table, NX_Solt solt)
{
    if (!table)
    {
        return NX_NULL;
    }

    if (solt < table->usedCount)
    {
        return table->objectArray[solt];
    }

    return NX_NULL;
}

NX_Solt NX_ExposedObjectLocate(NX_ExposedObjectTable * table, void * object, NX_ExposedObjectType type)
{
    NX_Solt solt;
    NX_UArch level;
    NX_ExposedObject * exobj = NX_NULL;

    if (!table || !object)
    {
        return NX_SOLT_INVALID_VALUE;
    }

    if (NX_SpinLockIRQ(&table->lock, &level) != NX_EOK)
    {
        return NX_SOLT_INVALID_VALUE;
    }

    for (solt = 0; solt < table->usedCount; solt++)
    {
        exobj = table->objectArray[solt];

        if (exobj != NX_NULL && exobj->object == object && exobj->type == type)
        {
            NX_SpinUnlockIRQ(&table->lock, level);
            return solt;
        }
    }
    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_SOLT_INVALID_VALUE;
}

NX_Error NX_ExposedObjectInstall(NX_ExposedObjectTable * table, void * object, NX_ExposedObjectType type, NX_SoltCloseHandler handler,
    NX_SoltCloseHandler copyHandler, NX_Solt * outSolt)
{
    NX_Solt solt;
    NX_ExposedObject * exobj = NX_NULL;
    NX_UArch level;

    if (!table)
    {
        return NX_EINVAL;
    }

    if (NX_SpinLockIRQ(&table->lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }

    solt = NX_ExposedObjectAlloc(table);
    if (solt < 0)
    {
        NX_SpinUnlockIRQ(&table->lock, level);
        return NX_ENORES;
    }

    exobj = table->objectArray[solt];

    exobj->object = object;
    exobj->type = type;
    exobj->close = handler;
    exobj->copy = copyHandler;

    if (outSolt)
    {
        *outSolt = solt;
    }

    NX_LOG_D("install exobj: task=%d solt=%d object=%p/%s", 
        NX_ThreadSelf()->tid, solt, exobj->object, NX_ExposedObjectGetNameByType(exobj->type));

    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_EOK;
}

NX_Error NX_ExposedObjectInstallTo(NX_ExposedObjectTable * table, void * object, NX_ExposedObjectType type, NX_SoltCloseHandler handler,
    NX_SoltCloseHandler copyHandler, NX_Solt solt)
{
    NX_ExposedObject * exobj = NX_NULL;
    NX_UArch level;

    if (!table)
    {
        return NX_EINVAL;
    }

    if (NX_SpinLockIRQ(&table->lock, &level) != NX_EOK)
    {
        return NX_EINTR;
    }

    solt = NX_ExposedObjectAllocFrom(table, solt);
    if (solt < 0)
    {
        NX_SpinUnlockIRQ(&table->lock, level);
        return NX_ENORES;
    }

    exobj = table->objectArray[solt];

    exobj->object = object;
    exobj->type = type;
    exobj->close = handler;
    exobj->copy = copyHandler;

    NX_LOG_D("install exobj: task=%d solt=%d object=%p/%s", 
        NX_ThreadSelf()->tid, solt, exobj->object, NX_ExposedObjectGetNameByType(exobj->type));

    NX_SpinUnlockIRQ(&table->lock, level);
    return NX_EOK;
}

NX_Error NX_ExposedObjectUninstalll(NX_ExposedObjectTable * table, NX_Solt solt)
{
    NX_ExposedObject * exobj = NX_NULL;
    NX_UArch level;
    NX_Error err;

    if (!table)
    {
        return NX_EINVAL;
    }
    
    if (solt >= table->usedCount)
    {
        return NX_EINVAL;
    }

    exobj = table->objectArray[solt];

    if (!exobj)
    {
        return NX_EOK; /* no need uninstall */
    }

    NX_LOG_D("uninstall exobj: task=%d solt=%d object=%p/%s",
        NX_ThreadSelf()->tid, solt, exobj->object, NX_ExposedObjectGetNameByType(exobj->type));

    NX_AtomicDec(&exobj->reference);

    if (NX_AtomicGet(&exobj->reference) <= 0 && exobj->close)
    {
        err = exobj->close(exobj->object, exobj->type);
        if (err != NX_EOK)
        {
            NX_LOG_E("do close solt %d error %s, object type:%s", solt,
                NX_ErrorToString(err),NX_ExposedObjectGetNameByType(exobj->type));
        }
    }

    NX_Thread * self = NX_ThreadSelf();
    self->flags |= NX_THREAD_FLAG_UNINTERRUPTABLE;
    NX_SpinLockIRQ(&table->lock, &level);
    self->flags &= ~NX_THREAD_FLAG_UNINTERRUPTABLE;

    /* destroy exobj */
    NX_ExposedObjectFree(table, exobj, solt);

    NX_SpinUnlockIRQ(&table->lock, level);

    return NX_EOK;
}

NX_Error NX_ExposedObjectCopy(NX_ExposedObjectTable * dst, NX_ExposedObjectTable * src, NX_Solt solt, NX_Solt * outSolt)
{
    NX_ExposedObject * exobj;
    NX_Solt newSolt;
    NX_UArch level;
    NX_Error err;

    if (!dst || !src)
    {
        return NX_EINVAL;
    }

    exobj = NX_ExposedObjectGet(src, solt);
    if (!exobj)
    {
        return NX_EFAULT;
    }
    /* alloc exobj  */
    newSolt = NX_ExposedObjectGetFreeSolt(dst);
    if (newSolt < 0)
    {
        return NX_ENORES;
    }

    if (exobj->copy != NX_NULL)
    {
        NX_ExposedObjectInstallTo(dst, exobj->object, exobj->type,
            exobj->close, exobj->copy, newSolt);

        err = exobj->copy(exobj->object, exobj->type);
        if (err != NX_EOK)
        {
            NX_LOG_E("do copy solt %d error %s, object type:%s", solt,
                NX_ErrorToString(err),NX_ExposedObjectGetNameByType(exobj->type));
        }
    }
    else
    {
        NX_SpinLockIRQ(&dst->lock, &level);
        NX_ExposedObjectRefAndBind(dst, newSolt, exobj);
        NX_SpinUnlockIRQ(&dst->lock, level);
    }

    if (outSolt)
    {
        *outSolt = newSolt;
    }

    return NX_EOK;
}

NX_Error NX_ExposedObjectCopyTo(NX_ExposedObjectTable * dst, NX_ExposedObjectTable * src, NX_Solt destSolt, NX_Solt srcSolt)
{
    NX_ExposedObject * srcExobj;
    NX_UArch level;
    NX_Error err;

    if (!dst || !src)
    {
        return NX_EINVAL;
    }

    /* same solt, do nothing */
    if (dst == src && destSolt == srcSolt)
    {
        return NX_EOK;
    }

    /* get source */
    srcExobj = NX_ExposedObjectGet(src, srcSolt);
    if (!srcExobj)
    {
        return NX_EFAULT;
    }
    
    /* get dest */
    if (NX_ExposedObjectGet(dst, destSolt) != NX_NULL)
    {
        /* close dest if exist */
        err = NX_ExposedObjectUninstalll(dst, destSolt);
        if (err != NX_EOK)
        {
            return err;
        }
    }

    /* need a new exobj for dest */
    if (srcExobj->copy != NX_NULL)
    {
        NX_ExposedObjectInstallTo(dst, srcExobj->object, srcExobj->type,
            srcExobj->close, srcExobj->copy, destSolt);

        err = srcExobj->copy(srcExobj->object, srcExobj->type);
        if (err != NX_EOK)
        {
            NX_LOG_E("do copy solt %d error %s, object type:%s", destSolt,
                NX_ErrorToString(err),NX_ExposedObjectGetNameByType(srcExobj->type));
        }
    }
    else
    {
        NX_SpinLockIRQ(&dst->lock, &level);
        NX_ExposedObjectRefAndBind(dst, destSolt, srcExobj);
        NX_SpinUnlockIRQ(&dst->lock, level);
    }

    return NX_EOK;
}

NX_UserObject *NX_UserObjectCreate(void *object, NX_U32 pid)
{
    NX_UserObject *uobj = NX_MemAlloc(sizeof(NX_UserObject));
    if (uobj == NX_NULL)
    {
        return NX_NULL;
    }

    uobj->object = object;
    uobj->pid = pid;

    return uobj;
}

NX_Error NX_UserObjectDestroy(NX_UserObject *object)
{
    if (object == NX_NULL)
    {
        return NX_EINVAL;
    }

    NX_MemFree(object);

    return NX_EOK;
}
